/*
 * The contents of this web application are subject to the Mozilla Public License Version 
 * 1.1 (the "License"); you may not use this web application except in compliance with 
 * the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/.
 * 
 * Software distributed under the License is distributed on an "AS IS" basis, 
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 
 * for the specific language governing rights and limitations under the License.
 * 
 * The Original Code is owned by and the Initial Developer of the Original Code is 
 * Composite A/S (Danish business reg.no. 21744409). All Rights Reserved
 * 
 * Section 11 of the License is EXPRESSLY amended to include a provision stating 
 * that any dispute, including but not limited to disputes related to the enforcement 
 * of the License, to which Composite A/S as owner of the Original Code, as Initial 
 * Developer or in any other role, becomes a part to shall be governed by Danish law 
 * and be initiated before the Copenhagen City Court ("K�benhavns Byret")            
 */

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Xsl;
using Composite.C1Console.Security;
using Composite.Core.Collections.Generic;
using Composite.Core.Extensions;
using Composite.Core.IO;
using Composite.Core.Localization;
using Composite.Core.Logging;
using Composite.Core.Xml;
using Composite.Data;
using Composite.Data.Streams;
using Composite.Data.Types;
using Composite.Functions;
using Composite.Functions.ManagedParameters;
using Composite.Functions.Plugins.FunctionProvider;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ObjectBuilder;
using Composite.Core;
using Composite.Core.Configuration;


namespace Composite.Plugins.Functions.FunctionProviders.XsltBasedFunctionProvider
{
    /// <summary>    
    /// </summary>
    /// <exclude />
    [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] 
    public enum OutputXmlSubType
    {
        /// <exclude />
        XHTML = 0,

        /// <exclude />
        XML = 1
    }



    /// <summary>    
    /// </summary>
    /// <exclude />
    [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] 
    [ConfigurationElementType(typeof(XsltBasedFunctionProviderData))]
    public sealed class XsltBasedFunctionProvider : IDynamicTypeFunctionProvider
    {
        private FunctionNotifier _functionNotifier;


        /// <exclude />
        public XsltBasedFunctionProvider()
        {
            DataEventSystemFacade.SubscribeToStoreChanged<IXsltFunction>(OnDataChanged, false);
        }



        /// <exclude />
        public FunctionNotifier FunctionNotifier
        {
            set { _functionNotifier = value; }
        }



        /// <exclude />
        public IEnumerable<IFunction> DynamicTypeDependentFunctions
        {
            get
            {
                return
                    (from f in DataFacade.GetData<IXsltFunction>().ToList()
                     select new XsltXmlFunction(f) as IFunction).ToList();
            }
        }



        /// <exclude />
        public IEnumerable<IFunction> Functions
        {
            get
            {
                yield break;
            }
        }



        private void OnDataChanged(object sender, StoreEventArgs storeEventArgs)
        {
            if (!SystemSetupFacade.SetupIsRunning)
            {
                _functionNotifier.FunctionsUpdated();
            }
        }



        /// <exclude />
        public static void ResolveImportIncludePaths(XContainer doc)
        {
            IEnumerable<XElement> imports = doc.Descendants().Where(f => f.Name == Namespaces.Xsl + "import" || f.Name == Namespaces.Xsl + "include").ToList();
            foreach (XElement import in imports)
            {
                XAttribute hrefAttribute = import.Attribute("href");
                if (hrefAttribute != null && hrefAttribute.Value.StartsWith("~/"))
                {
                    hrefAttribute.Value = PathUtil.Resolve(hrefAttribute.Value);
                }
            }
        }




        private sealed class XsltXmlFunction : IFunction
        {
            private readonly IXsltFunction _xsltFunction; // go through XsltFunction instead of this
            private IEnumerable<ParameterProfile> _parameterProfiles;
            private volatile IEnumerable<NamedFunctionCall> _FunctionCalls;
            private readonly object _lock = new object();
            private bool _subscribedToFileChanges;
            private readonly Hashtable<CultureInfo, XslCompiledTransform> _xslTransformations = new Hashtable<CultureInfo, XslCompiledTransform>();


            public XsltXmlFunction(IXsltFunction xsltFunction)
            {
                _xsltFunction = xsltFunction;
            }




            public object Execute(ParameterList parameters, FunctionContextContainer context)
            {
                Guid xsltFunctionId = this._xsltFunction.Id;

                if (_FunctionCalls == null)
                {
                    lock (_lock)
                    {
                        if (_FunctionCalls == null)
                        {
                            _FunctionCalls = RenderHelper.GetValidatedFunctionCalls(xsltFunctionId);
                        }
                    }
                }

                TransformationInputs transformationInput = RenderHelper.BuildInputDocument(_FunctionCalls, parameters, false);

                XDocument newTree = new XDocument();

                using (XmlWriter writer = new LimitedDepthXmlWriter(newTree.CreateWriter()))
                {
                    XslCompiledTransform xslTransformer = GetXslCompiledTransform();

                    XsltArgumentList transformArgs = new XsltArgumentList();
                    XslExtensionsManager.Register(transformArgs);

                    if (transformationInput.ExtensionDefinitions != null)
                    {
                        foreach (IXsltExtensionDefinition extensionDef in transformationInput.ExtensionDefinitions)
                        {
                            transformArgs.AddExtensionObject(extensionDef.ExtensionNamespace.ToString(), extensionDef.EntensionObjectAsObject);
                        }
                    }

                    xslTransformer.Transform(transformationInput.InputDocument.CreateReader(), transformArgs, writer);
                }

                if (this._xsltFunction.OutputXmlSubType == "XHTML")
                {

                    return new XhtmlDocument(newTree);
                }
                return newTree.Root;
            }



            private XslCompiledTransform GetXslCompiledTransform()
            {
                CultureInfo currentCultureInfo = LocalizationScopeManager.CurrentLocalizationScope;
                XslCompiledTransform xslCompiledTransform;

                if (_xslTransformations.TryGetValue(currentCultureInfo, out xslCompiledTransform))
                {
                    return xslCompiledTransform;
                }

                lock (_lock)
                {
                    if (!_xslTransformations.TryGetValue(currentCultureInfo, out xslCompiledTransform))
                    {
                        using (
                            DebugLoggingScope.CompletionTime(this.GetType(), "Loading and compiling {0}".FormatWith(_xsltFunction.XslFilePath)))
                        {
                            string folderPath = Path.GetDirectoryName(_xsltFunction.XslFilePath);
                            string fileName = Path.GetFileName(_xsltFunction.XslFilePath);

                            IXsltFile xsltFileHandle = null;

                            try
                            {
                                var xsltFileHandles =
                                    (from file in DataFacade.GetData<IXsltFile>()
                                     where String.Equals(file.FolderPath, folderPath, StringComparison.OrdinalIgnoreCase)
                                           && String.Equals(file.FileName, fileName, StringComparison.OrdinalIgnoreCase)
                                     select file).ToList();

                                Verify.That(xsltFileHandles.Count == 1, "XSLT file path {0} found {1} times. Only one instance was expected.".FormatWith(_xsltFunction.XslFilePath, xsltFileHandles.Count));
                                xsltFileHandle = xsltFileHandles[0];
                            }
                            catch (Exception ex)
                            {
                                Log.LogError("XsltBasedFunctionProvider", ex);    
                                throw;
                            }

                            if(!_subscribedToFileChanges)
                            {
                                xsltFileHandle.SubscribeOnChanged(ClearCachedData);
                                _subscribedToFileChanges = true;
                            }

                            xslCompiledTransform = new XslCompiledTransform();


                            XDocument doc;
                            using (Stream xsltSourceStream = xsltFileHandle.GetReadStream())
                            {
                                using (XmlReader xmlReader = XmlReader.Create(xsltSourceStream))
                                {
                                    doc = XDocument.Load(xmlReader);
                                }
                            }

                            ResolveImportIncludePaths(doc);

                            LocalizationParser.Parse(doc);

                            xslCompiledTransform.Load(doc.CreateReader(), XsltSettings.TrustedXslt, new XmlUrlResolver());

                            _xslTransformations.Add(currentCultureInfo, xslCompiledTransform);
                        }
                    }
                }
                return xslCompiledTransform;
            }

            private void ClearCachedData(string filePath, FileChangeType changeType)
            {
                lock(_lock)
                {
                    _xslTransformations.Clear();
                }
            }


            public string Name
            {
                get
                {
                    return _xsltFunction.Name;
                }
            }



            public string Namespace
            {
                get
                {
                    return _xsltFunction.Namespace;
                }
            }


            public string Description
            {
                get
                {
                    return _xsltFunction.Description;
                }
            }



            public Type ReturnType
            {
                get
                {
                    switch (this._xsltFunction.OutputXmlSubType)
                    {
                        case "XHTML":
                            return typeof(XhtmlDocument);
                        default:
                            return typeof(XElement);
                    }
                }
            }



            public IEnumerable<ParameterProfile> ParameterProfiles
            {
                get
                {
                    if (_parameterProfiles == null)
                    {
                        lock (_lock)
                        {
                            if (_parameterProfiles == null)
                            {
                                _parameterProfiles = ManagedParameterManager.GetParameterProfiles(_xsltFunction.Id);
                            }
                        }
                    }
                    return _parameterProfiles;
                }
            }



            public EntityToken EntityToken
            {
                get
                {
                    return _xsltFunction.GetDataEntityToken();
                }
            }
        }

    }



    [Assembler(typeof(NonConfigurableFunctionProviderAssembler))]
    internal sealed class XsltBasedFunctionProviderData : FunctionProviderData
    {
    }
}
